///PHILOTES Source Code.  (C)2012 PhiloLabs
#pragma once

#ifndef _Material_H__
#define _Material_H__

#include "OgrePrerequisites.h"

#include "OgreResource.h"
#include "OgreIteratorWrappers.h"
#include "OgreCommon.h"
#include "util/colourValue.h"
#include "OgreBlendMode.h"

namespace Ogre {

	// Forward declaration
	class MaterialPtr;
    class LodStrategy;

	
	
	
    class _OgreExport Material : public Resource
    {
        friend class SceneManager;
        friend class MaterialManager;

    public:
        /// distance list used to specify LOD
		typedef vector<Real>::type LodValueList;
        typedef ConstVectorIterator<LodValueList> LodValueIterator;
    protected:


        
        void applyDefaults(void);

        typedef vector<Technique*>::type Techniques;
		/// All techniques, supported and unsupported
        Techniques mTechniques;
		/// Supported techniques of any sort
        Techniques mSupportedTechniques;
		typedef map<unsigned short, Technique*>::type LodTechniques;
        typedef map<unsigned short, LodTechniques*>::type BestTechniquesBySchemeList;
		
        BestTechniquesBySchemeList mBestTechniquesBySchemeList;

        LodValueList mUserLodValues;
        LodValueList mLodValues;
        const LodStrategy *mLodStrategy;
        bool mReceiveShadows;
		bool mTransparencyCastsShadows;
        /// Does this material require compilation?
        bool mCompilationRequired;
		/// Text description of why any techniques are not supported
		String mUnsupportedReasons;

		
		void insertSupportedTechnique(Technique* t);

		
		void clearBestTechniqueList(void);

		
		void prepareImpl(void);

		
		void unprepareImpl(void);

		
		void loadImpl(void);

		
		void unloadImpl(void);
		/// @copydoc Resource::calculateSize
		size_t calculateSize(void) const { return 0; } // TODO 
    public:

        
		Material(ResourceManager* creator, const String& name, ResourceHandle handle,
			const String& group, bool isManual = false, ManualResourceLoader* loader = 0);

        ~Material();
        
        Material& operator=( const Material& rhs );

        
        bool isTransparent(void) const;

        
        void setReceiveShadows(bool enabled) { mReceiveShadows = enabled; }
        
        bool getReceiveShadows(void) const { return mReceiveShadows; }

		
		void setTransparencyCastsShadows(bool enabled) { mTransparencyCastsShadows = enabled; }
		
		bool getTransparencyCastsShadows(void) const { return mTransparencyCastsShadows; }

        
        Technique* createTechnique(void);
        
        Technique* getTechnique(unsigned short index);
        
        Technique* getTechnique(const String& name);
		
        unsigned short getNumTechniques(void) const;
        		
        void removeTechnique(unsigned short index);		
        
        void removeAllTechniques(void);
        typedef VectorIterator<Techniques> TechniqueIterator;
        
        TechniqueIterator getTechniqueIterator(void);
        
        TechniqueIterator getSupportedTechniqueIterator(void);
		
		
        Technique* getSupportedTechnique(unsigned short index);
		
        unsigned short getNumSupportedTechniques(void) const;
		
		const String& getUnsupportedTechniquesExplanation() const { return mUnsupportedReasons; }

        
        unsigned short getNumLodLevels(unsigned short schemeIndex) const;
        
        unsigned short getNumLodLevels(const String& schemeName) const;

        
        Technique* getBestTechnique(unsigned short lodIndex = 0, const Renderable* rend = 0);


        
        MaterialPtr clone(const String& newName, bool changeGroup = false, 
			const String& newGroup = StringUtil::BLANK) const;

        
        void copyDetailsTo(MaterialPtr& mat) const;

        
        void compile(bool autoManageTextureUnits = true);

        // -------------------------------------------------------------------------------
        // The following methods are to make migration from previous versions simpler
        // and to make code easier to write when dealing with simple materials
        // They set the properties which have been moved to Pass for all Techniques and all Passes

        
        void setPointSize(Real ps);

        
        void setAmbient(Real red, Real green, Real blue);

        
        void setAmbient(const ColourValue& ambient);

        
        void setDiffuse(Real red, Real green, Real blue, Real alpha);

        
        void setDiffuse(const ColourValue& diffuse);

        
        void setSpecular(Real red, Real green, Real blue, Real alpha);

        
        void setSpecular(const ColourValue& specular);

        
        void setShininess(Real val);

        
        void setSelfIllumination(Real red, Real green, Real blue);

        
        void setSelfIllumination(const ColourValue& selfIllum);

		
        void setDepthCheckEnabled(bool enabled);

        
        void setDepthWriteEnabled(bool enabled);

        
        void setDepthFunction( CompareFunction func );

		
		void setColourWriteEnabled(bool enabled);

        
        void setCullingMode( CullingMode mode );

        
        void setManualCullingMode( ManualCullingMode mode );

        
        void setLightingEnabled(bool enabled);

        
        void setShadingMode( ShadeOptions mode );

        
        void setFog(
            bool overrideScene,
            FogMode mode = FOG_NONE,
			const ColourValue& colour = ColourValue::White,
            Real expDensity = 0.001, Real linearStart = 0.0, Real linearEnd = 1.0 );

        
        void setDepthBias(float constantBias, float slopeScaleBias);

        
        void setTextureFiltering(TextureFilterOptions filterType);
        
        void setTextureAnisotropy(int maxAniso);

        
        void setSceneBlending( const SceneBlendType sbt );

        
        void setSeparateSceneBlending( const SceneBlendType sbt, const SceneBlendType sbta );

        
        void setSceneBlending( const SceneBlendFactor sourceFactor, const SceneBlendFactor destFactor);

        
        void setSeparateSceneBlending( const SceneBlendFactor sourceFactor, const SceneBlendFactor destFactor, const SceneBlendFactor sourceFactorAlpha, const SceneBlendFactor destFactorAlpha);

        
        void _notifyNeedsRecompile(void);

        
        void setLodLevels(const LodValueList& lodValues);

        
        LodValueIterator getLodValueIterator(void) const;

        
        LodValueIterator getUserLodValueIterator(void) const;

        
        ushort getLodIndex(Real value) const;

        
        const LodStrategy *getLodStrategy() const;
        
        void setLodStrategy(LodStrategy *lodStrategy);

        
        void touch(void) 
        { 
            if (mCompilationRequired) 
                compile();
            // call superclass
            Resource::touch();
        }
        
        
        bool applyTextureAliases(const AliasTextureNamePairList& aliasList, const bool apply = true) const;

	    
	    bool getCompilationRequired() const
	    {
		    return mCompilationRequired;
	    }


    };

	
	class _OgreExport MaterialPtr : public SharedPtr<Material> 
	{
	public:
		MaterialPtr() : SharedPtr<Material>() {}
		explicit MaterialPtr(Material* rep) : SharedPtr<Material>(rep) {}
		MaterialPtr(const MaterialPtr& r) : SharedPtr<Material>(r) {} 
		MaterialPtr(const ResourcePtr& r) : SharedPtr<Material>()
		{
			// lock & copy other mutex pointer
            OGRE_MUTEX_CONDITIONAL(r.OGRE_AUTO_MUTEX_NAME)
            {
			    OGRE_LOCK_MUTEX(*r.OGRE_AUTO_MUTEX_NAME)
			    OGRE_COPY_AUTO_SHARED_MUTEX(r.OGRE_AUTO_MUTEX_NAME)
			    pRep = static_cast<Material*>(r.getPointer());
			    pUseCount = r.useCountPointer();
			    if (pUseCount)
			    {
				    ++(*pUseCount);
			    }
            }
		}

		/// Operator used to convert a ResourcePtr to a MaterialPtr
		MaterialPtr& operator=(const ResourcePtr& r)
		{
			if (pRep == static_cast<Material*>(r.getPointer()))
				return *this;
			release();
			// lock & copy other mutex pointer
            OGRE_MUTEX_CONDITIONAL(r.OGRE_AUTO_MUTEX_NAME)
            {
			    OGRE_LOCK_MUTEX(*r.OGRE_AUTO_MUTEX_NAME)
			    OGRE_COPY_AUTO_SHARED_MUTEX(r.OGRE_AUTO_MUTEX_NAME)
			    pRep = static_cast<Material*>(r.getPointer());
			    pUseCount = r.useCountPointer();
			    if (pUseCount)
			    {
				    ++(*pUseCount);
			    }
            }
			else
			{
				// RHS must be a null pointer
				assert(r.isNull() && "RHS must be null if it has no mutex!");
				setNull();
			}
			return *this;
		}
	};
	
	

} //namespace 

#endif
